package FlexTrade;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import common_classes.AuctionPrimitiveData;
import common_classes.AuctionStates;
import common_classes.IAuction;
import common_classes.IMarketService;
import common_classes.WSEvent;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.entry.Entry;
import net.jini.core.event.RemoteEvent;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.event.UnknownEventException;
import net.jini.core.lease.Lease;
import net.jini.core.lease.UnknownLeaseException;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lookup.ServiceTemplate;
import net.jini.discovery.DiscoveryEvent;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.lease.LeaseRenewalManager;
import net.jini.lookup.ServiceDiscoveryManager;
import net.jini.lookup.ServiceIDListener;
import net.jini.lookup.entry.Name;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import FlexTrade.Auctions.DutchAuctionImpl;
import FlexTrade.Auctions.EnglishAuctionImpl;
import FlexTrade.Auctions.VickreyAuctionImpl;

/*
 * Implementation of IMarketService interface		
 */

public class MarketServiceImpl implements IMarketService, DiscoveryListener, 
								RemoteEventListener, ServiceIDListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = 293205672305151027L;
	private FileOutputStream fos = null;
	private ObjectOutputStream oos = null;
	private File file_auction = null;
	private Hashtable<String, AuctionPrimitiveData> listOfCurrentAuctions;
	//private TransactionManager myTxnMgr = null;
	private AbstractAuction newAuctionRequest = null;
	
	Hashtable<String, Lease> auctionLeases;
	LeaseRenewalManager leaseMgr;
	
	private Configuration config;

	public MarketServiceImpl() {
		// TODO Auto-generated constructor stub
		//lookUpLUS();
		listOfCurrentAuctions = new Hashtable<String, AuctionPrimitiveData>();
		auctionLeases = new Hashtable<String, Lease>();
		leaseMgr = new LeaseRenewalManager();
		file_auction = new File(Market.CHECK_POINT_FILE_PATH);
		try {
			config = ConfigurationProvider.getInstance(new String[]{Market.CONFIG_FILE});
		} catch (ConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	//a provider request a new auction
	@Override
	public synchronized boolean receiveNewAuctionRequestFromProvider(AuctionPrimitiveData auctionPrimitiveData, 
										RemoteEventListener provider) throws RemoteException {
		// TODO Auto-generated method stub
		//create new auction object for this request
		int _eventID = 0;
		switch(auctionPrimitiveData.auctionType) {
			case(IAuction.VICKREY_AUCTION_TYPE):
				newAuctionRequest = new VickreyAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.V_NEW_AUCTION;
				break;

			case(IAuction.ENGLISH_AUCTION_TYPE):
				newAuctionRequest = new EnglishAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.E_NEW_AUCTION;
				break;
				
			case(IAuction.DUTCH_AUCTION_TYPE):
				newAuctionRequest = new DutchAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.D_NEW_AUCTION;
				break;
				
			default:
				System.out.println("<MarketServiceImp.receiveAuctionRequest> Auction type is invalid.");
				return false;
		}
		//register this object as a service to LUS
		this.registerAuctionObjectToLUS(newAuctionRequest);

		//Add provider as event listener so that provider will be updated about the status of this auction
		newAuctionRequest.customerJoinAuction(auctionPrimitiveData.provider.getUniqueProviderID());
		newAuctionRequest.putEventToPool(_eventID);
		listOfCurrentAuctions.put(auctionPrimitiveData.uniqueAuctionID, auctionPrimitiveData);
		//set the time duration that the auction will be held
		newAuctionRequest.setAuctionTimeDuration(auctionPrimitiveData.timeDuration);
		newAuctionRequest.addRemoteListener(this);
		return true;
	}
	
	public synchronized Hashtable<String, AuctionPrimitiveData> getCurrentActiveAuctions() throws RemoteException{
		String key;
		IAuction t_auction = null;
		Enumeration<String> keys = listOfCurrentAuctions.keys();
		while (keys.hasMoreElements()) {
			key = keys.nextElement();
			t_auction = lookUpAuction(key);
			if (t_auction == null) {
				listOfCurrentAuctions.remove(key);
			}
		}
		return this.listOfCurrentAuctions;
	}

	@Override
	public void discarded(DiscoveryEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void discovered(DiscoveryEvent arg0) {
		// TODO Auto-generated method stub
		/*listOfCurrentAuctions = new Hashtable<String, AuctionPrimitiveData>();
		ServiceRegistrar[] registrars = arg0.getRegistrars();
		try {
			fos = new FileOutputStream(file_auction);
			oos = new ObjectOutputStream(fos);
		} catch (Exception e) {
			e.printStackTrace();
		}

		Class[] classes = new Class[] {IAuction.class};
		ServiceTemplate template = new ServiceTemplate(null, classes, null);
		for (int n = 0; n < registrars.length; n++) {
			ServiceRegistrar registrar = registrars[n];
			ServiceMatches matches;
			try {
				matches = registrar.lookup(template, 10);
			} catch(java.rmi.RemoteException e) {
				e.printStackTrace();
				continue;
			}
			// NB: matches.totalMatches may be greater than matches.items.length
			for (int m = 0; m < matches.items.length; m++) {
				IAuction auction = (IAuction) matches.items[m].service;
				try {
					listOfCurrentAuctions.put(auction.getUniqueAuctionID(), auction.getAuctionPrimitiveData());
				} catch (RemoteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}*/
	}


	
	@Override
	public void serviceIDNotify(ServiceID arg0) {
		// TODO Auto-generated method stub
		System.out.println("Auction " + arg0.toString() + " is successfully placed on FlexTrade");
	}
	
	

	@Override
	public void notify(RemoteEvent arg0) throws UnknownEventException,
			RemoteException {
		// TODO Auto-generated method stub
		IAuction sourceAuction = (IAuction)arg0.getSource();
		int eventID = (int)arg0.getID();
		String sourceUniqueAuctionID = sourceAuction.getUniqueAuctionID();
		Lease lease;
		switch(eventID){
			//there is error with payment transaction. This auction is aborted and re-opened again
			case IAuction.CANNOT_TRANSFER_PAYMENT:
				//remove from list of current auctions
				listOfCurrentAuctions.remove(sourceUniqueAuctionID);
				//remove lease from hashtable
				lease = auctionLeases.remove(sourceUniqueAuctionID);
				try {
					lease.cancel();
				} catch (UnknownLeaseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				/*
				lease = auctionLeases.remove(sourceUniqueAuctionID);
				//renew the lease
				long extendedDuration = sourceAuction.getAuctionPrimitiveData().timeDuration
											+ IMarketService.TIME_DELAY_AFTER_AUCTION_FINISH;
				leaseMgr.renewFor(lease, extendedDuration, null);
				//put back to hash table
				auctionLeases.put(sourceUniqueAuctionID, lease);*/
				break;
			//payment transfered successfully. Remove the lease from hash table	
			case IAuction.PAYMENT_TRANSFERED_SUCCESSFULLY:
				//remove from list of current auctions
				listOfCurrentAuctions.remove(sourceUniqueAuctionID);
				//remove lease from hashtable
				lease = auctionLeases.remove(sourceUniqueAuctionID);
				try {
					lease.cancel();
				} catch (UnknownLeaseException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				break;
			default:
		}
		
	}

	@Override
	public synchronized void setCheckPoint() throws RemoteException {
		// TODO Auto-generated method stub
		try {
			fos = new FileOutputStream(file_auction);
			oos = new ObjectOutputStream(fos);
		} catch (Exception e) {
			e.printStackTrace();
		}
		AuctionPrimitiveData t_auctionPrimitiveData;
		IAuction t_auction;
		String key;
		AuctionStates t_au_state;
		Enumeration<String> keys = listOfCurrentAuctions.keys();
		while (keys.hasMoreElements()) {
			key = keys.nextElement();
			t_auctionPrimitiveData = listOfCurrentAuctions.get(key);
			t_auction = lookUpAuction(key);
			if (t_auction != null) {
				try {
					t_au_state = t_auction.getCurrentAuctionStates();
					//If got problem of premature Garbage Collector, it will
					//jump to exception and not be written to file 
					oos.writeObject(t_auctionPrimitiveData);
					oos.writeObject(t_au_state);
					oos.writeObject(t_auction.getEventPool());
				} catch (IOException e1) {
					//remove this finished auction from list of current auctions
					listOfCurrentAuctions.remove(key);
					e1.printStackTrace();
				} 
			}
		}
		try {
			oos.writeObject(null);		//mark the end of file
			oos.flush();
			oos.close();
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public synchronized void restoreFromCheckPoint() throws RemoteException {
		// TODO Auto-generated method stub

		AuctionPrimitiveData res_auc_data = null;
		AuctionStates res_state = null;
		listOfCurrentAuctions = new Hashtable<String, AuctionPrimitiveData>();
		Hashtable<String, ArrayList<WSEvent>> _eventPool = null;
		//restore auction objects from /auctionobjects.dat
		try {
			FileInputStream fin = new FileInputStream(file_auction);
			ObjectInputStream ois = new ObjectInputStream(fin);
			while (true) {
				res_auc_data = (AuctionPrimitiveData) ois.readObject();
				if (res_auc_data == null) {
					break;
				}
				res_state = (AuctionStates) ois.readObject();
				_eventPool = (Hashtable<String, ArrayList<WSEvent>>) ois.readObject();
				restoreAuctionToLUS(res_auc_data, res_state, _eventPool);
			}
			ois.close();
			fin.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private IAuction lookUpAuction(String _uniqueAuctionID) {
		ServiceDiscoveryManager clientMgr = null;
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
																	null, // unicast locators
																	null); // DiscoveryListener
			clientMgr = new ServiceDiscoveryManager(mgr, new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
		}
		//Create template to find FlexTrade service by name		
		Entry[] serverAttributes = new Entry[1];
	    serverAttributes[0] = new Name (_uniqueAuctionID);
	    ServiceTemplate template = 
	    		  new ServiceTemplate (null, null, serverAttributes);

		ServiceItem item = null;
		// Try to find the service, blocking until timeout if necessary
		try {
			item = clientMgr.lookup(template,
										null, // no filter
										10000); // timeout
		} catch(Exception e) {
			e.printStackTrace();
		}
		if (item == null) {
			System.out.println("No auction service.");
			return null;
		}
		// Get the service
		IAuction t_auction = (IAuction) item.service;
		if (t_auction != null) {
			ProxyPreparer preparer = null;
			try {
				preparer = (ProxyPreparer) config.getEntry("ProxyPreparer", "preparer", ProxyPreparer.class);
			} catch (Exception e) {
				e.printStackTrace();
				preparer = new BasicProxyPreparer();
			}
			
			try {
				t_auction = (IAuction) preparer.prepareProxy(t_auction);
				return t_auction;
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}
	
	private boolean restoreAuctionToLUS(AuctionPrimitiveData auctionPrimitiveData,
									AuctionStates auctionStates, 
									Hashtable<String, ArrayList<WSEvent>> _eventPool) {
		// TODO Auto-generated method stub
		//create new auction object for this request
		int _eventID = 0;
		switch(auctionPrimitiveData.auctionType) {
			case(IAuction.VICKREY_AUCTION_TYPE):
				newAuctionRequest = new VickreyAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.V_NEW_AUCTION;
				break;
			
			case(IAuction.ENGLISH_AUCTION_TYPE):
				newAuctionRequest = new EnglishAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.E_NEW_AUCTION;
				break;
			
			case(IAuction.DUTCH_AUCTION_TYPE):
				newAuctionRequest = new DutchAuctionImpl(auctionPrimitiveData);
				_eventID = IAuction.D_NEW_AUCTION;
				break;
			
			default:
				System.out.println("<MarketServiceImp.receiveAuctionRequest> Auction type is invalid.");
				return false;
		}
		//register this object as a service to LUS
		try {
			this.registerAuctionObjectToLUS(newAuctionRequest);
			//Add provider as event listener so that provider will be updated about the status of this auction
			newAuctionRequest.customerJoinAuction(auctionPrimitiveData.provider.getUniqueProviderID());
			newAuctionRequest.putEventToPool(_eventID);
			listOfCurrentAuctions.put(auctionPrimitiveData.uniqueAuctionID, auctionPrimitiveData);
			//set the time duration that the auction will be held
			newAuctionRequest.setAuctionTimeDuration(auctionPrimitiveData.timeDuration);
			//restore the auction states and event poolright before crashing
			newAuctionRequest.setAuctionStates(auctionStates);
			newAuctionRequest.setEventPool(_eventPool);
			//add this market object as remote listener
			newAuctionRequest.addRemoteListener(this);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return true;
	}
	
	private void registerAuctionObjectToLUS(IAuction auctionObject) throws RemoteException {
		Remote auctionProxy = null;
		try {
			/*Exporter exporter = new BasicJeriExporter(
					TcpServerEndpoint.getInstance(0),
					new BasicILFactory());
					// export an object of this class*/
			Exporter exporter = (Exporter) config.getEntry("SSLJeriExport", "exporter", Exporter.class);
			try {
				auctionProxy = (Remote) exporter.export((Remote) auctionObject);
				auctionObject.setProxy(auctionProxy);
			} catch (ExportException e) {
				e.printStackTrace();
			}			
		} catch(Exception e) {
			System.err.println(e.toString());
			e.printStackTrace();
			System.exit(1);
		}


		LookupLocator lookup = null;
		ServiceRegistrar registrar = null;
		try {
			lookup = new LookupLocator(Market.LUS);
		} catch(java.net.MalformedURLException e) {
			System.err.println("Lookup failed: " + e.toString());
		}
		
		try {
			registrar = lookup.getRegistrar();
		} catch (java.io.IOException e) {
			System.err.println("Registrar search failed: " + e.toString());
		} catch (java.lang.ClassNotFoundException e) {
			System.err.println("Registrar search failed: " + e.toString());
		}
		//create template
		Entry[] serverAttributes = new Entry[1];
	    serverAttributes[0] = new Name (auctionObject.getUniqueAuctionID());
		ServiceItem item = new ServiceItem(null, auctionProxy, serverAttributes);
		ServiceRegistration reg = null;
		try {
			//desired lease time
			long desiredDuration = auctionObject.getAuctionPrimitiveData().timeDuration 
										+ IMarketService.TIME_DELAY_AFTER_AUCTION_FINISH;
			reg = registrar.register(item, desiredDuration);
			//this lease managed by lease renewal manager
			auctionLeases.put(auctionObject.getUniqueAuctionID(), reg.getLease());
		} catch(java.rmi.RemoteException e) {
			System.err.println("Register exception: " + e.toString());
		}
		
		/*
		try {
			LookupDiscoveryManager mgr = new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
					null, // unicast locators
					null); // DiscoveryListener
			//template
			Entry[] serverAttributes = new Entry[1];
		    serverAttributes[0] = new Name (auctionObjec.getUniqueAuctionID());
			ServiceItem item = new ServiceItem(null, auctioProxy, serverAttributes);
		    ServiceRegistrar[] registrars = mgr.getRegistrars();
		    ServiceRegistration reg = null;
		    for(int i = 0; i< registrars.length; i++){
		    	ServiceRegistrar registrar = registrars[i];
		    	long desiredDuration = auctionObjec.getAuctionPrimitiveData().timeDuration 
										+ IMarketService.TIME_DELAY_AFTER_AUCTION_FINISH;
				reg = registrar.register(item, desiredDuration);
				auctionLeases.put(auctionObjec.getUniqueAuctionID(), reg.getLease());
		    }
		    
		    /*new JoinManager(proxy, // service proxy
				    serverAttributes, // attr sets
					this, // ServiceIDListener
					mgr, // DiscoveryManager
					new LeaseRenewalManager());
		} catch(Exception e) {
			e.printStackTrace();
		}
		*/
	}

}